#ifndef ITEMSET_CONTAINER_H
#define ITEMSET_CONTAINER_H

#include "itemset.h"
#include <exception>
#include <stdexcept>
#include <list>

using namespace std;

/**
 * In this namespace all classes included, which are implementing the 
 * apriori algorithm.
 * \version 0.1
 * \author Michael Opitz
 */
namespace apriori {
	template <class transaction,
		  template <class> class collection
		 >
	/**
	 * This class implements a collection for the itemsets, and provides
	 * several methods for crossproducts of sets for the itemsets.
	 */
	class itemset_container {
	public:
		typedef itemset<transaction, collection> t_item;

		typedef typename transaction::data_ptr data_ptr;
		typedef typename transaction::data_coll data_coll; 
		typedef typename transaction::ptr trans_ptr;
		typedef collection<trans_ptr> t_trans_coll;

		itemset_container():_card(0) {}
		~itemset_container() {}

		list<t_item>& get_items() {return _items;}
		const list<t_item>& get_items() const {return _items;}

		/**
		 * deletes all itemsets with support < s
		 */
		void cut(int support, float confidence)
		{
			typedef typename list<t_item>::iterator it;
			it iter = _items.begin(), lim = _items.end();
			while(iter != lim) {
				if(iter->get_support() < support ||
				   iter->get_confidence() < confidence) {
					_items.erase(iter++);
				} else {
					iter++;
				}
			}
		}

		/**
		 * Creates the initial unique itemsets 
		 */
		void initialize(const t_trans_coll &coll)
		{
			// first of all: create list of all items...
			list<data_ptr> list;
			typedef typename t_trans_coll::const_iterator it;
			for(it i = coll.begin(); i != coll.end(); ++i) {
				append_list(list, (*i)->get_elements());
			}
			// create unique list...
			create_itemsets(list);
			calc_support(coll);
			_card = 0;
		}

		/**
		 * Calculates the support of each itemset with the transactions
		 * in col
		 */
		void calc_support(const t_trans_coll &col)
		{
			typedef typename list<t_item>::iterator it;
			for(it i = _items.begin(); i != _items.end(); ++i)
			{
				i->check_support(col);
			}
		}

		/**
		 * generates new itemsets with a new cardinality
		 */
		void generate(t_trans_coll &trans)
		{
			if(_items.empty()) {
				throw runtime_error(
				"Error, no itemsets are available");
			}
			// here we go...
			_card++;
			find_itemsets();
			calc_support(trans);
		}
		template <class T>
		void write(T &t) 
		{
			typedef typename list<t_item>::iterator it;
			for(it i = _items.begin(); i != _items.end(); ++i) {
				i->write(t);
				t << endl;
			}
		}
	private:
		/**
		 * Checks if s occures in l
		 */
		bool duplicate(const list<t_item> &l, const t_item &s) const {
			typedef typename list<t_item>::const_iterator it;
			for(it i = l.begin(); i != l.end(); ++i) {
				if(*i == s) {
					return true;
				}
			}
			return false;
		}
		/**
		 * Multiplies the set t_item with col and stores the result
		 * in l
		 */
		void multiply(list<t_item> &l, t_item &s, data_coll &col)
		{
			typedef typename data_coll::iterator it;
			for(it i = col.begin(); i != col.end(); ++i) {
				if(!s.find(*i)) {
					t_item tmp(s);
					tmp.get_items().push_back(*i);
					if(!duplicate(l, tmp)) {
						l.push_back(tmp);
					}
				}
			}
		}
		/**
		 * Creates the crossproduct of _items and col
		 */
		void make_crossproduct(data_coll &col)
		{
			list<t_item> new_items;
			typedef typename list<t_item>::iterator it;
			for(it i = _items.begin(); i != _items.end(); ++i) {
				multiply(new_items, *i, col);
			}
			_items = new_items;
		}
		/**
		 * Returns true if ptr was found in col
		 */
		template <class T>
		bool found(T &col, const data_ptr &ptr)
		{
			typedef typename T::iterator it;
			for(it i = col.begin(); i != col.end(); ++i) {
				if((**i) == (*ptr)) {
					return true;
				}
			}
			return false;
		}
		/**
		 * Appends all distinct elements of col into l.
		 */
		void get_unique_set_data(data_coll &col, 
					 const list<data_ptr> &l)
		{
			typedef typename list<data_ptr>::const_iterator it;
			for(it i = l.begin(); i != l.end(); ++i) {
				if(!found(col, *i)) {
					col.push_back(*i);
				}
			}
		}
		/**
		 * Finds all unique items in _items...
		 */
		data_coll get_unique_set()
		{
			data_coll col;
			typedef typename list<t_item>::iterator it;
			for(it i = _items.begin(); i != _items.end(); ++i) {
				get_unique_set_data(col, (*i).get_items());
			}
			return col;
		}
		/**
		 * Generates new itemsets with the cardinality _card
		 */
		void find_itemsets()
		{
			// make the crossproduct with all unique sets...
			data_coll col = get_unique_set();
			// make the crossproduct...
			make_crossproduct(col);
		}
		/**
		 * appends the items of col to list
		 */
		void append_list(list<data_ptr> &list, const data_coll &col)
		{
			typedef typename data_coll::const_iterator it;
			for(it i = col.begin(); i != col.end(); ++i) {
				if(!found(list, *i)) {
					list.push_back(*i);
				}
			}
		}

		/**
		 * creates itemsets out of l
		 */
		void create_itemsets(const list<data_ptr> &l)
		{
			_items.clear();
			typedef typename list<data_ptr>::const_iterator it;
			for(it i = l.begin(); i != l.end(); ++i) {
				data_coll tmp;
				tmp.push_back(*i);
				t_item set(tmp);
				_items.push_back(set);
			}
		}

		list<t_item> _items;
		int _card;
	};
}

#endif
